 package org.externalassets
{
	import flash.display.LoaderInfo;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.ProgressEvent;
	import flash.net.URLRequest;
	import flash.system.ApplicationDomain;
	import flash.system.LoaderContext;
	import flash.system.SecurityDomain;
	
	/**
	 * Class used to load one or more SWFs.  Automatically registers the SWFs
	 * to the current application domain.
	 * 
	 *   - If you are trying to load swfs for the express purpose of accessing symbols
	 *     within those swfs, wait for the Event.INIT, do not wait for Event.COMPLETE,
	 *     the swfs will not be accessible.
	 * 
	 *   - You can add ProgressEvent.PROGRESS, Event.INIT and Event.COMPLETE
	 *     listeners to this class.  Whether you have one swf or a hundred swfs,
	 *     the class will abstract that from you and give you status events as
	 *     if there was only one swf being loaded.  For example, the currentBytes
	 *     values of ProgressEvent will be filled with the sum of all of the current
	 *     swfs being loaded, the same goes for the totalBytes.  COMPLETE will be
	 *     dispatched when everything has been loaded, INIT will be dispatched when
	 *     everything has been loaded and is accessible.
	 * 
	 *   - Once the objects are loaded, the loaders are accessible in the loaderArray.
	 *     Just make sure you wait until Event.Complete is dispatched at a minimum.
	 * 
	 * @author crebstock
	 *  
	 */	
	public class AssetLoader extends EventDispatcher
	{
		/**
		 * True if all loaders have finished loading
		 * 
		 * @default null
		 */
		public var allLoaded : Boolean;
		
		/**
		 * True if the methods and properties of all
		 * loaded objects are accessible
		 * 
		 * @default null 
		 */		
		public var allReady : Boolean;
		
		/**
		 * True if the Event.Complete notification
		 * has already been dispatched, notifying
		 * listeners that all loading has completed
		 *
		 * @default null
		 */
		public var alreadyNotifiedComplete : Boolean;

		/**
		 * True if the Event.Init notification
		 * has already been dispatched, notifying
		 * listeners that all loaded objects are
		 * accessible
		 *
		 * @default null
		 */
		public var alreadyNotifiedInit : Boolean;
		
		/**
		 * The sum of all the currentBytes properties of
		 * the Loaders in the loadArray
		 * 
		 * @default null
		 */
		public var bytesLoaded : Number;
		
		/**
		 * The sum of all the totalBytes properties of
		 * the Loaders in the loadArray
		 * 
		 * @default null
		 */
		public var bytesTotal : Number;

		/**
		 * Array of loaders
		 * 
		 * @default null
		 */		
		protected var __loaderArray : Array;		
		
		
		/**
		 * Security domain for the loaders
		 * 
		 * @default null
		 */
		private var __secDom:SecurityDomain = null;
		
		
		public function AssetLoader() 
		{
			bytesLoaded = 0;
			bytesTotal = 0;			
		}
		
		public function get loaderArray() : Array { return __loaderArray; }
		
		/**
		 * Initializes loaders, adds listeners and starts loading on
		 * all files, loaders or url's passed in.
		 *  
		 * @param loadObjs an array of strings, urlrequests or loaders
		 * 
		 */		
		public function load( ...loadObjs ) : void
		{
			//Initialize Loader array
			__loaderArray = createLoaderArray( loadObjs );

			//Create and add listeners to the load array
			addLoadArrayListeners();
			
			//liftoff!
			beginLoading();
		}

		/**
		 * Adds listeners to the load array
		 * 
		 */		
		protected function addLoadArrayListeners() : void
		{
			for( var i : Number = 0; i < __loaderArray.length; i++ )
			{
				__loaderArray[i].contentLoaderInfo.addEventListener( ProgressEvent.PROGRESS, onLoaderProgress );
				__loaderArray[i].contentLoaderInfo.addEventListener( Event.COMPLETE, onLoaderComplete );
				__loaderArray[i].contentLoaderInfo.addEventListener( Event.INIT, onLoaderInit );
			}
		}
		
		/**
		 * Called when every load is complete, removes complete and progress event listeners and
		 * sets the alreadyNotified boolean to true to prevent duplicate notifications
		 * 
		 */
		protected function allLoadsComplete() : void
		{
			alreadyNotifiedComplete = true;
			
			for( var i : Number = 0; i < __loaderArray.length; i++ )
			{
				__loaderArray[i].contentLoaderInfo.removeEventListener( ProgressEvent.PROGRESS, onLoaderProgress );
				__loaderArray[i].contentLoaderInfo.removeEventListener( Event.COMPLETE, onLoaderComplete );
			}
		}
		
		/**
		 * Called when every load has passed back an init event indicating its properties and 
		 * methods are accessible, removes init event listener and sets the alreadyNotified 
		 * boolean to true to prevent duplicate notifications
		 * 
		 */
		protected function allLoadsReady() : void
		{
			alreadyNotifiedInit = true;
			
			for( var i : Number = 0; i < __loaderArray.length; i++ )
			{
				__loaderArray[i].contentLoaderInfo.removeEventListener( Event.INIT, onLoaderInit );
			}
		}
		
		/**
		 * Iterates through the loader array to start each individual loader
		 * 
		 */		
		protected function beginLoading() : void
		{
			for( var i : Number = 0; i < __loaderArray.length; i++ )
			{
				__loaderArray[i].load( __loaderArray[i].loaderURL, new LoaderContext( false, ApplicationDomain.currentDomain, __secDom ) );
			}
		}

		/**
		 * Creates an array of loaders equal in length to the loaderURLArray
		 * 
		 * @return Array
		 */
		protected function createLoaderArray( unParsedObjs : Array ) : Array
		{
			var tmpArray : Array = new Array();
			
			var tmpLoaderObj : AssetLoaderObject;
			
			for( var i : Number = 0; i < unParsedObjs.length; i++ )
			{				
				tmpLoaderObj = new AssetLoaderObject();
				
				//If current object is a string, create a loader from the string
				if( unParsedObjs[i] is String )
				{
					tmpLoaderObj.loaderURL = new URLRequest( unParsedObjs[i] );
					tmpLoaderObj.loaderInit = false;
					tmpLoaderObj.loaderStatus = false;
					
					tmpArray.push( tmpLoaderObj );
				}				
			}
			
			return tmpArray; 			
		}	
		
		/**
		 * Iterates through the loaderArray to see if all loaders
		 * have finished loading, sets allLoaded to true if this 
		 * is the case
		 */
		protected function checkForAllLoaded() : void
		{
			var tmpLoaded : Boolean = true;
			
			for( var i : Number = 0; i < __loaderArray.length; i++ )
			{
				if( __loaderArray[i].loaderStatus != true )
					tmpLoaded = false;
			}
			
			allLoaded = tmpLoaded;
		}
		
		/**
		 * Iterates through the loaderArray to see if the methods
		 * and properties of all loaders are accessible, sets 
		 * allLoaded to true if this is the case
		 */
		protected function checkForAllReady() : void
		{
			var tmpReady : Boolean = true;
			
			for( var i : Number = 0; i < __loaderArray.length; i++ )
			{
				if( __loaderArray[i].loaderReady != true )
					tmpReady = false;
			}
			
			allReady = tmpReady;
		}
		
		/**
		 * Iterates through the loaderArray and returns the location of the loader
		 * 
		 * @return Number
		 */
		protected function locateLoader( ldr : LoaderInfo ) : Number
		{
			//Iterate through the loaders to find out which one issued
			//the event (probably a faster than 0(n) way to do this)
			for( var i : Number = 0; i < __loaderArray.length; i++ )
			{
				if( ldr == __loaderArray[i].contentLoaderInfo )
				{
					//Once you find it, return its location
					return i;
				}
			}
			
			//You should never get here, but if you do then 0 is as good an answer as any
			return 0;
		}
		
		protected function onLoaderProgress( evt : ProgressEvent ) : void
		{
			updateLoadedPercentage( locateLoader( evt.target as LoaderInfo ) );
		}
		
		protected function onLoaderComplete( evt : Event ) : void
		{
			updateLoadedPercentage( locateLoader( evt.target as LoaderInfo ) );
		}

		protected function onLoaderInit( evt : Event ) : void
		{
			updateLoadedInit( locateLoader( evt.target as LoaderInfo ) );
		}

		protected function updateBytesLoaded() : void
		{
			bytesLoaded = 0;
			
			for( var i : Number = 0; i < __loaderArray.length; i++ )
			{
				bytesLoaded += __loaderArray[i].contentLoaderInfo.bytesLoaded;
			}
		}
		
		/**
		 * Calculates the new curLoaded variable and updates the curLoaderArray
		 * element that was changed.  Also dispatches a load complete event
		 * if all loaders have finished.
		 * 
		 * @param updateLoc location in the arrays that got updated
		 * 
		 */
		protected function updateLoadedPercentage( updateLoc : Number ) : void
		{
			//if this is the first bit of load information we've gotten
			//from this particular loader, add its total to the
			//bytesTotal property
			if( !__loaderArray[updateLoc].loaderInit )
			{
				bytesTotal += __loaderArray[updateLoc].contentLoaderInfo.bytesTotal;
				
				__loaderArray[updateLoc].loaderInit = true;				
			}
			
			//if this loader has finished loading, set its loaderStatus property to true
			if( __loaderArray[updateLoc].contentLoaderInfo.bytesLoaded == __loaderArray[updateLoc].contentLoaderInfo.bytesTotal )
			{
				//loading has finished for this loader object
				__loaderArray[updateLoc].loaderStatus = true;
			}
			
			updateBytesLoaded();
			
			checkForAllLoaded();
			
			if( !allLoaded )
			{
				dispatchEvent( new ProgressEvent( ProgressEvent.PROGRESS, 
												  false, 
												  false, 
												  bytesLoaded, 
												  bytesTotal ) );
			}
																  
			else
			{
				allLoadsComplete();
				dispatchEvent( new Event( Event.COMPLETE, false, false ) );
			}
		}
		
		protected function updateLoadedInit( updateLoc : Number ) : void
		{
			__loaderArray[updateLoc].loaderReady = true;
			
			checkForAllReady();
			
			if( allReady )
			{
				allLoadsReady();
				dispatchEvent( new Event( Event.INIT, false, false ) );
			}
		}
		
	}
}